﻿using System;
using System.Collections.Generic;
using System.Reflection;
using Myotragus.Data.Util;

namespace Myotragus.Data.Emit
{
	public abstract class TypeGenerator : ITypeGenerator
	{
		#region Constants

		protected const MethodAttributes PropertyAccessorMethodAttributes =
			MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
			MethodAttributes.Virtual | MethodAttributes.NewSlot;

		#endregion Constants

		#region Fields

		protected readonly IDictionary<Type, Type> DicImplementationCache = new Dictionary<Type, Type>();

		public IDynamicAssembly DynamicAssembly
		{
			get;
			protected set;
		}

		#endregion Fields

		#region Creation And Initialization

		protected TypeGenerator(IDynamicAssembly dynamicAssembly)
		{
			DynamicAssembly = dynamicAssembly;
		}

		#endregion Creation And Initialization

		#region Service

		public Type GetImplementation(Type interfaceType)
		{
			if (interfaceType == null) throw new ArgumentNullException("interfaceType");
			if (!interfaceType.IsInterface) throw new ArgumentException();

			if (DicImplementationCache.ContainsKey(interfaceType))
				return DicImplementationCache[interfaceType];

			try
			{
				return DicImplementationCache[interfaceType] =
							GenerateImplementation(interfaceType).CreateType();
			}
			catch (Exception ex)
			{
				throw ExecutionHelper.CreateException<TypeGenerationException>(ex,
					"Couldn't generate implementation for type: '{0}'", interfaceType.FullName);
			}
		}

		#endregion Service

		#region Protected Tools

		protected abstract IDynamicType GenerateImplementation(Type interfaceType);

		protected virtual IDynamicType DefineType(string typeName, TypeAttributes attributes,
			Type baseType, Type[] interfaces)
		{
			return DynamicAssembly.DefineType(typeName, attributes, baseType, interfaces);
		}

		#endregion Protected Tools
	}
}